/*
* Fall 2008: Computer Graphics B581
* lighting template code
*/

#include <stdio.h>
#include <stdlib.h>
// #include <unistd.h>
#include <math.h>

#ifdef WIN32
#include <windows.h>
#endif

#if defined (__APPLE__) || defined(MACOSX)
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include "readoff.h"
#include "readppm.h"


/*****************************************************************/
/******************** MY FUNCTION DEFINITIONS ********************/
/*****************************************************************/

static void myInitGL(void);
static void myInitData(int *argc, char **argv);
//static void myInitMenus (void);
static void myInitCheckers(void);
static void displayObject();

/* my callback functions needed for GLUT event handling */
static void myHandleMenu(int value);
static void myDisplay(void);
static void myReshape(int width, int height);
static void mouseMotion(int curx, int cury);
static void mouseButton(int button, int state, int x, int y);
static void myVisibility(int state);
static void keyboard(unsigned char key, int x, int y);
//static void myMenus (int value);


/***************************************************************/
/******************** MY GLOBAL CONSTANTS **********************/
/***************************************************************/

#define MY_MAX(A,B) ((A) >= (B) ? (A) : (B))

/* my constants for GLUT window defaults */
#define MY_WIND_X_SIZE     600
#define MY_WIND_Y_SIZE     600
#define MY_WIND_X_POS      100
#define MY_WIND_Y_POS      100

/* my menu IDs for GLUT menu-handling functions */
#define MY_MENU_EXIT     -9999
#define MY_MENU_RESET     -200
#define MY_MENU_CAMERA      20
#define MY_MENU_LIGHT       30
#define MY_MENU_OBJECT      40
#define MY_MENU_PROJECT     100

#define	MY_CHECKERS_WIDTH   64
#define	MY_CHECKERS_HEIGHT  64


/***************************************************************/
/******************** MY GLOBAL VARIABLES **********************/
/***************************************************************/

/* globals for generated texture - modified from Red Book's original checker.c - */
static GLubyte CheckersImg[MY_CHECKERS_HEIGHT][MY_CHECKERS_WIDTH][3];
static GLuint  TextureName;

static TexMap  TMap;   /* global for texture from readppm */

static GLfloat LightPos[4];

/* user interaction */
static GLint InteractionMode;
static GLint XmouseDown, YmouseDown;
static GLint XmouseCurrent, YmouseCurrent;
static GLint XmousePrev, YmousePrev, DX, DY;
static GLint WindHeight, WindWidth;
static GLint KeybModifiers;
static GLint mouseButtons = 0;

/* transformations */
static GLdouble  TX, TY, TZ;

/* 3D Projection */
static GLdouble XCamera, YCamera, ZCamera;
static GLdouble FOVangle, FOVratio, ClipNear, ClipFar;


int  X=0, Y=0;
GLfloat XC, YC, ZC;

Object3D object;
Point3 *facenormals;
float focal, viewDist;
Point3 objectCenter;

float mat[16];


///////////////////////////////////////////////////////////////////
// Vector and Point algebra

Vector3 makeVector(HPoint3 from, HPoint3 to)
{
	Vector3 v;
	v.x = to.x - from.x;
	v.y = to.y - from.y;
	v.z = to.z - from.z;
	return v;
}

Vector3 crossProduct(Vector3 v1, Vector3 v2)
{
	Vector3 v;
	v.x = v1.y * v2.z - v2.y * v1.z;
	v.y = v1.z * v2.x - v2.z * v1.x;
	v.z = v1.x * v2.y - v2.x * v1.y;
	return v;
}

Vector3 makeUnitVector(Vector3 v)
{
	double d;
	d = v.x * v.x + v.y * v.y + v.z * v.z;
	d = sqrt(d);
	v.x = v.x / d;
	v.y = v.y / d;
	v.z = v.z / d;
	return v;
}

Vector3 makeUnitNormal(HPoint3 x1, HPoint3 x2, HPoint3 x3)
{
	Vector3 v1, v2, v;
	v1 = makeVector(x1, x2);
	v2 = makeVector(x1, x3);
	v = crossProduct(v1, v2);
	v = makeUnitVector(v);
	return v;
}


/**************************** MAIN *****************************/


int main(int argc, char **argv) {
	glutInit(&argc,argv);

	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowPosition(MY_WIND_X_POS, MY_WIND_Y_POS);
	glutInitWindowSize(MY_WIND_X_SIZE, MY_WIND_Y_SIZE);

	glutCreateWindow("ps5 - Roshan James");

	glutDisplayFunc       (myDisplay);
	glutReshapeFunc       (myReshape);
	glutMouseFunc         (mouseButton);
	glutMotionFunc        (mouseMotion);
	glutPassiveMotionFunc (NULL);
	glutIdleFunc          (NULL);
	glutKeyboardFunc      (keyboard);
	glutVisibilityFunc    (myVisibility);

	fprintf(stderr,"Press ESC or 'q' to Exit.\n");

	myInitGL();
	myInitData(&argc,argv);  /* pass command line arguments for texture */

	/* initialize GLUT menu handler as last one, to take over access
	to GLUT_RIGHT_BUTTON, regardles to what other handlers did to it: */
	//myInitMenus();

	glutMainLoop();
	return(EXIT_SUCCESS);
} /* main() */


/* --------------------------------------- */
/*           initialize my data            */
/* --------------------------------------- */

void myInitData(int *argc, char **argv) {
	int  i;
	char s[1024];
	InteractionMode = MY_MENU_CAMERA;

	TX = 1.1; TY = 0.8; TZ = -2.0;
	glMatrixMode(GL_MODELVIEW);
	glTranslated(TX, TY, TZ);

	XmouseDown = -1;
	YmouseDown = -1;
	XmouseCurrent = XmousePrev = -1;
	YmouseCurrent = YmousePrev = -1;
	DX = DY = 0;
	KeybModifiers = 0;

	/* initialize texturing */
	//TextureOn = GL_TRUE;

	fprintf(stderr,"\n ps5: argc = %d: ",*argc);
	for (i = 0; i< *argc; i++)
		fprintf(stderr, " arg[%d] = %s, ",i, argv[i]);
	fprintf(stderr,"\n");

	//////////////////////////////////////////////////////////////////////
	// Read OFF file
	if (*argc >= 2)
	{
		printf("Reading in %s.\n", argv[1]);
		ReadOFF(argv[1], &object);
	}
	else
	{
		printf("Using default Cube object.\n");
		ReadOFF("cube.off", &object);
	}

	//XC = YC = 0.0;
	//ZC = viewDist;

	// calculate normals
	facenormals = (Point3 *) malloc(object.Nfaces*sizeof(Point3));
	for(i = 0; i < object.Nfaces; i++)
	{
		facenormals[i] = makeUnitNormal(
			object.vertices[object.faces[i][0]], 
			object.vertices[object.faces[i][1]], 
			object.vertices[object.faces[i][2]]);
	}

	// calculate center for rotations
	objectCenter.x = objectCenter.y = objectCenter.z = 0;
	for(i = 0; i < object.Nvertices; i++)
	{
		objectCenter.x += object.vertices[i].x;
		objectCenter.y += object.vertices[i].y;
		objectCenter.z += object.vertices[i].z;
	}
	objectCenter.x = objectCenter.x / object.Nvertices;
	objectCenter.y = objectCenter.y / object.Nvertices;
	objectCenter.z = objectCenter.z / object.Nvertices;


	//////////////////////////////////////////////////////////////////////////////////////
	// Read Textures

	i = -1;
	if (*argc == 3) {
	    // load image from URL specified on command line
	    sprintf(s,"./getppm.pl %s > temp.ppm", argv[2]);
	    printf("Executing:\n   %s\n", s);
	    system(s);
	    i = ReadPPM("temp.ppm", &TMap);
	    //i = ReadPPM(argv[2], &TMap);
	} 
	//else {
	//    /* load image from default URL */
	//    printf("Using default texture file 'fermat_raw.ppm'.\n");
	//    system("./getppm.pl http://www.cs.indiana.edu/~mitja/Delo/B581/fermat_raw.ppm > temp.ppm");
	//    i = ReadPPM("temp.ppm", &TMap);
	//}

	if (i > 0) {
		printf("Using texture from file.\n"); /* image file is valid: use it as texture */
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glGenTextures(1, &TextureName);
		glBindTexture(GL_TEXTURE_2D, TextureName);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TMap.texc, TMap.texr, 
			0, GL_RGB, GL_UNSIGNED_BYTE, TMap.texture);
	} else {
		printf("Using checkers texture.\n");  /* image file is invalid: use checkers */
		myInitCheckers();
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glGenTextures(1, &TextureName);
		glBindTexture(GL_TEXTURE_2D, TextureName);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MY_CHECKERS_WIDTH, MY_CHECKERS_HEIGHT, 
			0, GL_RGB, GL_UNSIGNED_BYTE, CheckersImg);        
	}
} /* myInitData() */


/* --------------------------------------- */

void myInitCheckers(void) {
	int i, j, c;

	for (i = 0; i < MY_CHECKERS_HEIGHT; i++) {
		for (j = 0; j < MY_CHECKERS_WIDTH; j++) {
			c = ((((i&0x8)==0)^((j&0x8))==0))*255;
			CheckersImg[i][j][0] = (GLubyte) c;
			CheckersImg[i][j][1] = (GLubyte) c;
			CheckersImg[i][j][2] = (GLubyte) c;
		}
	}
} /* myInitCheckers() */




/***************************************************************/
/*********************** MOUSE CALLBACKS ***********************/
/***************************************************************/

/* ------------------------------------------------------------------------ */
/* Mouse Button  UP/DOWN  or DOWN/UP transition only */
/* ------------------------------------------------------------------------ */
void mouseButton(int button, int state, int x, int y) {  

	XmouseDown = x;
	YmouseDown = WindHeight - y - 1;	/* Invert to right-handed system */
	button = (1 << button);


	/* This permits detection of left, middle, left+middle */
	if (state == GLUT_DOWN) {
		mouseButtons |= button;
	}
	else {
		mouseButtons &= (~(button));
	}

	/* bitmask with GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_ALT */
	KeybModifiers = glutGetModifiers();

	if (button == 1 || button == 2) {
		if (state == GLUT_DOWN) {
			XmouseCurrent = XmousePrev = XmouseDown;
			YmouseCurrent = YmousePrev = YmouseDown;
		} else {
			/* at button up, invalidate: */
			XmouseCurrent = XmousePrev = XmouseDown = -1;
			YmouseCurrent = YmousePrev = YmouseDown = -1;
			KeybModifiers = 0;
		}
		DX = DY = 0;
	}
	glutPostRedisplay();
} /* mouseButton() */


/* ------------------------------------------------------------------------ */
/* Mouse motion WHILE BUTTON DOWN  */
/*  COMPLAINS  if you call glutGetModifiers()  here!! */
/* ------------------------------------------------------------------------ */
void mouseMotion(int x, int y) {    
	XmouseCurrent = x;
	YmouseCurrent = WindHeight - y - 1;	/* Invert to right-handed system */

	DX = XmouseCurrent - XmousePrev;
	DY = YmouseCurrent - YmousePrev;
	// printf("%d %d \n", DX, DY);

	XmousePrev = XmouseCurrent;
	YmousePrev = YmouseCurrent;

	switch(mouseButtons ){
	case 0x1:  /* Left Mouse Button   */
		break;
	case 0x2:  /* Middle Mouse Button */
		break;
	case 0x3:  /* Left+Middle Mouse Button */
		break;
	case 0x4:  /* Right Mouse Button (disabled with Menu)  */
		break;
	}

	glutPostRedisplay();
} /* mouseMotion(int x, int y) */


/* ------------------------------------------------------------------------ */
/* Handle Keyboard */
/* ------------------------------------------------------------------------ */
void keyboard(unsigned char key, int x, int y) {
	/*  Option: To force all lower case */
	if (key >= 'A' && key <= 'Z') key = (key-'A') + 'a';

	switch (key) {
	case 'h': fprintf(stderr,"Help: \n\
							 ESC or q: quit \n\
							 t: toggle texture\n\
							 ");
		break;
	case 'q' :
	case  27 :  /* 27 -> ESC */
		fprintf(stderr,"Normal Exit.\n");
		exit(EXIT_SUCCESS);
		break;

	default: fprintf(stderr,"Unhandled key: %c [%d] \n",key,key);
	}

	glutPostRedisplay();
} /* keyboard() */


/***************************************************************/
/************************** INIT GL ****************************/
/***************************************************************/

void myInitGL (void) {
	GLfloat light_ambient[] = {0.1,0.1,0.1, 1.0};
	GLfloat light_diffuse[] = {1.0,1.0,1.0, 1.0};
	GLfloat light_specular[] = {1.0,1.0,1.0, 1.0};

	/* projection pipeline initialization */
	FOVangle = 60.0;
	FOVratio = 1.0;
	ClipNear = 0.1;
	ClipFar = 1000.0;
	XCamera = YCamera = 0.7;
	ZCamera = 5.0;

	WindWidth = glutGet(GLUT_WINDOW_WIDTH);
	WindHeight  = glutGet(GLUT_WINDOW_HEIGHT);
	glViewport(0, 0, WindWidth, WindHeight);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	FOVratio  = (float)WindWidth / (float)WindHeight; /* keep viewed x/y ratio == 1 */
	/* instead of glFrustum(-1.0*ratio, 1.0*ratio, -1.0,1.0, FLength, 25.0);  */
	gluPerspective(FOVangle, FOVratio, ClipNear, ClipFar);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(-XCamera,-YCamera,-ZCamera);

	/* Colors, Lighting, Material  initialization */
	glClearColor( 0.4f, 0.6f, 0.5f, 1.0f);
	glPointSize(1);     glEnable(GL_POINT_SMOOTH);
	glLineWidth(1);     glEnable(GL_LINE_SMOOTH);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);    
	/* we do **not** want model materials to follow the current GL color: */
	glDisable(GL_COLOR_MATERIAL);

	glShadeModel(GL_SMOOTH);        /* or GL_FLAT */
	glEnable(GL_NORMALIZE);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	LightPos[0] = -1.0;    
	LightPos[1] = 2.0;
	LightPos[2] = 0.0;    
	LightPos[3] = 0.0;
	glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
} /* myInitGL() */



/***************************************************************/
/********************** DRAW FUNCTIONS *************************/
/***************************************************************/


void displayObject() {
	int i, j;
	Point3 p;
	GLfloat materialDiffuse[] = {0.7, 0.4, 0.2, 1.0};

	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, materialDiffuse);
	glColor3f(1,1, 1);

	for(i = 0; i < object.Nfaces; i++)
	{
		if(i == 0) {
			glEnable(GL_TEXTURE_2D);
			glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
			glBindTexture(GL_TEXTURE_2D, TextureName);
			glFrontFace(GL_CCW);
		}

		p.x = p.y = p.z = 0;
		fprintf(stderr, "Face Normals %d, %d, %d\n", facenormals[i].x, facenormals[i].y, facenormals[i].z);
		glNormal3f(facenormals[i].x, facenormals[i].y, facenormals[i].z);
		glBegin(GL_POLYGON);
		for(j = 0; j < object.nv_face[i]; j++)
		{

			p.x += object.vertices[object.faces[i][j]].x;
			p.y += object.vertices[object.faces[i][j]].y;
			p.z += object.vertices[object.faces[i][j]].z;

			if(j == 0) glTexCoord2d(0, 0);
			else if(j == 1) glTexCoord2d(1, 0);
			else if(j == 2) glTexCoord2d(1, 1);
			else glTexCoord2d(0, 1);

			glVertex3d(
				object.vertices[object.faces[i][j]].x,
				object.vertices[object.faces[i][j]].y,
				object.vertices[object.faces[i][j]].z);
		}
		glEnd();

		if(i == 0)
		{
			glFrontFace(GL_CW);        
			glDisable(GL_TEXTURE_2D);
		}

		p.x = p.x / object.nv_face[i];
		p.y = p.y / object.nv_face[i];
		p.z = p.z / object.nv_face[i];

		glDisable(GL_LIGHTING);
		glLineWidth(3.0);
		glBegin(GL_LINES);
		glColor3f(1.0,0.0,0.0);
		glVertex3f(p.x, p.y, p.z);
		glVertex3f(p.x + facenormals[i].x, p.y + facenormals[i].y, p.z + facenormals[i].z);
		glEnd();
		glEnable(GL_LIGHTING);
	}
} /* displayObject() */


Point3 getCurrentCenter()
{
	Point3 p;
	p.x = objectCenter.x * mat[0] + objectCenter.y*mat[4] + objectCenter.z * mat[8] + mat[12];
	p.y = objectCenter.x * mat[0+1] + objectCenter.y*mat[4+1] + objectCenter.z * mat[8+1] + mat[12+1];
	p.z = objectCenter.x * mat[0+2] + objectCenter.y*mat[4+2] + objectCenter.z * mat[8+2] + mat[12+2];
	return p;
}


/* ------------------------------------------------------------------------ */
void myDisplay(void) {
	Point3 p;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	if(mouseButtons == 1)
	{
		glMatrixMode(GL_MODELVIEW);
		glGetFloatv(GL_MODELVIEW_MATRIX, mat);
		if (KeybModifiers & GLUT_ACTIVE_SHIFT)
		{
			TZ = 0.01*(GLdouble)DX + 0.01 * DY;
			TX = TY = 0;
		}
		else
		{
			TX = 0.01*(GLdouble)DX;
			TY = 0.01*(GLdouble)DY;
			TZ = 0;
		}
		glLoadIdentity();
		glTranslatef(TX, TY, TZ);
		glMultMatrixf(mat);
	}
	if(mouseButtons == 4)
	{
		glMatrixMode(GL_MODELVIEW);
		glGetFloatv(GL_MODELVIEW_MATRIX, mat);
		glLoadIdentity();

		p = getCurrentCenter();
		glTranslated(p.x, p.y, p.z);
		if (KeybModifiers & GLUT_ACTIVE_SHIFT)
		{
			glRotated(DX, 0, 0, 1);
		}
		else
		{
			glRotated(DX, 0, 1, 0);
			glRotated(DY, -1, 0, 0);
		}
		glTranslated(-p.x, -p.y, -p.z);
		glMultMatrixf(mat);
	}

	displayObject();

	glutSwapBuffers();
} /* myDisplay() */


/***************************************************************/
/*********************** GLUT FUNCTIONS ************************/
/***************************************************************/


/* ------------------------------------------------------------------------ */
void myReshape(int w, int h) {
	w = MY_MAX(w,128);
	h = MY_MAX(h,128);
	WindHeight = h;
	WindWidth = w;
	glutReshapeWindow(w,h);
	glViewport(0,0,w,h);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	FOVratio  = (float)w / (float)h; /* keep world coord x/y ratio == 1 */
	gluPerspective(FOVangle, FOVratio, ClipNear, ClipFar);

	glMatrixMode(GL_MODELVIEW);
} /* myReshape() */

/* ------------------------------------------------------------------------ */
void myVisibility(int state) {
	/* ------------------------------------------------------------------------ */
	if (state == GLUT_VISIBLE){
		glutPostRedisplay();
	}
} /* myVisibility() */

/* ------------------------------------------------------------------------ */
